/*
 * Copyright (c) 2023 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include <mc/log.h>
#include <mc/filesystem.h>
#include <ctime>
#include <sys/stat.h>

#include "interface/network_adapter/log_collection.h"

namespace dev {

static void delete_old_log_files(const std::string& log_dir, 
                                  const std::vector<std::string>& curr_log_files) {
    // 使用mc::filesystem检查目录是否存在
    if (!mc::filesystem::is_directory(log_dir)) {
        return;  // 目录不存在
    }

    try {
        // 使用mc::filesystem列出目录中的所有文件
        auto files = mc::filesystem::list_files(log_dir);

        for (const auto& file_path : files) {
            std::string filename = mc::filesystem::basename(file_path);

            // 检查是否是当前生成的文件
            bool file_found = false;
            for (const auto& curr_file : curr_log_files) {
                if (curr_file == filename) {
                    file_found = true;
                    break;
                }
            }

            // 如果不是当前生成的文件，则删除
            if (!file_found) {
                if (mc::filesystem::remove(file_path)) {
                    ilog("deleted old log file: ${file}", ("file", file_path.string()));
                } else {
                    elog("failed to delete old log file: ${file}", ("file", file_path.string()));
                }
            }
        }
    } catch (const mc::filesystem::filesystem_error& e) {
        elog("Error listing directory ${dir}: ${error}", ("dir", log_dir)("error", e.what()));
    }
}

static std::string get_current_time_string() {
    auto now = std::time(nullptr);
    struct tm tm_buf;
    struct tm* tm = localtime_r(&now, &tm_buf);  // 线程安全版本
    if (!tm) {
        elog("localtime_r failed for log file timestamp");
        return "00000000000000";  // 返回默认时间戳作为fallback
    }

    char timestamp[16];  // "%Y%m%d%H%M%S" = 14字符 + null终止符
    strftime(timestamp, sizeof(timestamp), "%Y%m%d%H%M%S", tm);
    return std::string(timestamp);
}

bool NetworkAdapter_LogCollection::DumpErrorLog(const std::string& file_path) {
    // TODO: create a task to dump error log
    // 1. get the log file path from the request
    // 2. read the log file
    // 3. return progress and result
    return false; // return false if not implemented
}

bool NetworkAdapter_LogCollection::DumpLastWordLog(const std::string& file_path) {
    // TODO: create a task to dump last word log
    // 1. get the log file path from the request
    // 2. read the log file
    // 3. return progress and result
    return false; // return false if not implemented
}

bool NetworkAdapter_LogCollection::DumpRunningLog(const std::string& file_path) {
    // TODO: create a task to dump running log
    // 1. get the log file path from the request
    // 2. read the log file
    // 3. return progress and result
    return false; // return false if not implemented
}

bool NetworkAdapter_LogCollection::DumpBlackBoxLog(const std::string& file_path) {
    // TODO: create a task to dump black box log
    // 1. get the log file path from the request
    // 2. read the log file
    // 3. return progress and result
    return false; // return false if not implemented
}

void NetworkAdapter_LogCollection::init_ncsi_endpoint(ncsi_over_mctp_hw_ptr ncsi_endpoint) {
    m_ncsi_over_mctp_huawei = ncsi_endpoint;
}

void NetworkAdapter_LogCollection::init_smbus_obj(smbus_obj_ptr smbus_obj) {
    m_smbus_obj = smbus_obj;
}

bool NetworkAdapter_LogCollection::CollectLogByNcsi(
    const std::string& log_dir) {
    // 检查日志目录是否存在，如果不存在则返回失败
    if (!mc::filesystem::exists(log_dir)) {
        elog("Log directory does not exist: ${dir}", ("dir", log_dir));
        return false;
    }
    
    // 检查是否正在收集中
    if (m_is_log_collecting_by_ncsi.exchange(true)) {
        elog("NCSI log collection is already in progress");
        return false;
    }
    
    boost::asio::post(mc::get_work_context(), [this, log_dir]() mutable {
        // 使用RAII确保无论如何都会重置收集状态
        struct FlagGuard {
            std::atomic<bool>& flag;
            explicit FlagGuard(std::atomic<bool>& f) : flag(f) {}
            ~FlagGuard() { flag = false; }
        };
        FlagGuard flag_guard(m_is_log_collecting_by_ncsi);
        
        if (!m_ncsi_over_mctp_huawei) {
            elog("ncsi_endpoint is not initialized");
            return;
        }
        
        ilog("Start to collect NCSI logs for netcard");
        std::vector<std::string> log_files;
        
        bool ret = m_ncsi_over_mctp_huawei->collect_logs_by_ncsi(DEFAULT_PACKAGE_ID, log_files, log_dir);
        
        if (!ret) {
            elog("Failed to collect NCSI logs");
        }
        
        // 删除老的日志文件
        delete_old_log_files(log_dir, log_files);
        
        ilog("Finish to collect NCSI logs for netcard");
    });
    return true;  // 与SMBus逻辑一致，只要流程启动就返回成功
}

bool NetworkAdapter_LogCollection::CollectLogBySmbus(const std::string& log_dir) {
    // 检查日志目录是否存在，如果不存在则返回失败
    if (!mc::filesystem::exists(log_dir)) {
        elog("Log directory does not exist: ${dir}", ("dir", log_dir));
        return false;
    }
    
    // 检查是否正在收集中
    if (m_is_log_collecting_by_smbus.exchange(true)) {
        elog("SMBus log collection is already in progress");
        return false;
    }
    
    boost::asio::post(mc::get_work_context(), [this, log_dir]() mutable {
        // 使用RAII确保无论如何都会重置收集状态
        struct FlagGuard {
            std::atomic<bool>& flag;
            explicit FlagGuard(std::atomic<bool>& f) : flag(f) {}
            ~FlagGuard() { flag = false; }
        };
        FlagGuard flag_guard(m_is_log_collecting_by_smbus);
        
        if (!m_smbus_obj) {
            elog("smbus object is not initialized");
            return;
        }
        
        nlog("collect log for netcard by smbus start");
        std::vector<std::string> curr_log_files;
        
        // 获取错误日志 (opcode 0xC) - 每个日志类型单独获取时间戳
        std::string error_time_str = get_current_time_string();
        std::string error_log_file = "error_log_" + error_time_str + ".bin";
        std::string error_log_path = mc::filesystem::join(log_dir, error_log_file).string();
        curr_log_files.push_back(error_log_file);
        
        if (!get_log_by_smbus(0xC, error_log_path)) {
            elog("fetch error log by smbus failed");
        }
        
        // 获取临终遗言日志 (opcode 0xD)
        std::string last_word_time_str = get_current_time_string();
        std::string last_word_file = "last_word_" + last_word_time_str + ".bin";
        std::string last_word_path = mc::filesystem::join(log_dir, last_word_file).string();
        curr_log_files.push_back(last_word_file);
        
        if (!get_log_by_smbus(0xD, last_word_path)) {
            elog("fetch last word by smbus failed");
        }
        
        // 获取运行日志 (opcode 0xE)
        std::string running_time_str = get_current_time_string();
        std::string running_log_file = "running_log_" + running_time_str + ".bin";
        std::string running_log_path = mc::filesystem::join(log_dir, running_log_file).string();
        curr_log_files.push_back(running_log_file);
        
        if (!get_log_by_smbus(0xE, running_log_path)) {
            elog("fetch running log by smbus failed");
        }
        
        // 删除老的日志文件
        delete_old_log_files(log_dir, curr_log_files);
        
        nlog("collect log for netcard by smbus finished");
    });
    return true;  // 流程完成后返回成功
}

bool NetworkAdapter_LogCollection::get_log_by_smbus(uint16_t opcode, const std::string& log_path) {
    if (!m_smbus_obj) {
        elog("smbus object is not initialized");
        return false;
    }
    
    try {
        auto request = mc::mutable_dict{{"opcode", opcode}};
        auto rsp = m_smbus_obj->send_request(request);
        
        // 将响应数据写入文件 - 使用mc::filesystem（即使为空也创建文件）
        std::string content;
        if (rsp.empty()) {
            wlog("No log data received by smbus, opcode: 0x${opcode:X}, creating empty file", ("opcode", opcode));
            content = ""; // 空内容
        } else {
            content = std::string(reinterpret_cast<const char*>(rsp.data()), rsp.size());
        }
        
        if (!mc::filesystem::write_file(log_path, content)) {
            elog("Failed to write log file: ${path}", ("path", log_path));
            return false;
        }
        
        // 设置文件权限为0640 (owner读写，group只读)
        if (chmod(log_path.c_str(), S_IRUSR | S_IWUSR | S_IRGRP) != 0) {
            elog("Failed to set file permissions for: ${path}", ("path", log_path));
        }
        
        nlog("Successfully saved log data to: ${path}, size: ${size} bytes", 
             ("path", log_path)("size", rsp.size()));
        return true;
        
    } catch (const std::exception& e) {
        elog("Exception occurred while fetching log by smbus: ${error}", ("error", e.what()));
        return false;
    }
}

void NetworkAdapter_LogCollection::clean_ncsi_endpoint() {
    if (m_ncsi_over_mctp_huawei) {
        m_ncsi_over_mctp_huawei = nullptr;
    }
}

void NetworkAdapter_LogCollection::clean_smbus_obj() {
    if (m_smbus_obj) {
        m_smbus_obj = nullptr;
    }
}

} // namespace dev

MC_REFLECT(dev::NetworkAdapter_LogCollection)